<!DOCTYPE html>
<html>
  <head>
  <meta http-equiv="content-type" content="text/html; charset=utf-8">
  <meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0" name="viewport">
  <meta name="description" content="刘清政">
  <meta name="keyword" content="hexo-theme">
  
    <link rel="shortcut icon" href="/css/images/logo.png">
  
  <title>
    
      32-panic和recover | Justin-刘清政的博客
    
  </title>
  <link href="//cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet">
  <link href="//cdnjs.cloudflare.com/ajax/libs/nprogress/0.2.0/nprogress.min.css" rel="stylesheet">
  <link href="//cdnjs.cloudflare.com/ajax/libs/highlight.js/9.12.0/styles/tomorrow.min.css" rel="stylesheet">
  
<link rel="stylesheet" href="/css/style.css">

  
    
<link rel="stylesheet" href="/css/plugins/gitment.css">

  
  <script src="//cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
  <script src="//cdnjs.cloudflare.com/ajax/libs/geopattern/1.2.3/js/geopattern.min.js"></script>
  <script src="//cdnjs.cloudflare.com/ajax/libs/nprogress/0.2.0/nprogress.min.js"></script>
  
    
<script src="/js/qrious.js"></script>

  
  
    
<script src="/js/gitment.js"></script>

  
  

  
<meta name="generator" content="Hexo 4.2.0"></head>
<div class="wechat-share">
  <img src="/css/images/logo.png" />
</div>

  <body>
    <header class="header fixed-header">
  <div class="header-container">
    <a class="home-link" href="/">
      <div class="logo"></div>
      <span>Justin-刘清政的博客</span>
    </a>
    <ul class="right-list">
      
        <li class="list-item">
          
            <a href="/" class="item-link">主页</a>
          
        </li>
      
        <li class="list-item">
          
            <a href="/tags/" class="item-link">标签</a>
          
        </li>
      
        <li class="list-item">
          
            <a href="/archives/" class="item-link">归档</a>
          
        </li>
      
        <li class="list-item">
          
            <a href="/about/" class="item-link">关于我</a>
          
        </li>
      
    </ul>
    <div class="menu">
      <span class="icon-bar"></span>
      <span class="icon-bar"></span>
      <span class="icon-bar"></span>
    </div>
    <div class="menu-mask">
      <ul class="menu-list">
        
          <li class="menu-item">
            
              <a href="/" class="menu-link">主页</a>
            
          </li>
        
          <li class="menu-item">
            
              <a href="/tags/" class="menu-link">标签</a>
            
          </li>
        
          <li class="menu-item">
            
              <a href="/archives/" class="menu-link">归档</a>
            
          </li>
        
          <li class="menu-item">
            
              <a href="/about/" class="menu-link">关于我</a>
            
          </li>
        
      </ul>
    </div>
  </div>
</header>

    <div id="article-banner">
  <h2>32-panic和recover</h2>



  <p class="post-date">2019-03-10</p>
    <!-- 不蒜子统计 -->
    <span id="busuanzi_container_page_pv" style='display:none' class="">
        <i class="icon-smile icon"></i> 阅读数：<span id="busuanzi_value_page_pv"></span>次
    </span>
  <div class="arrow-down">
    <a href="javascript:;"></a>
  </div>
</div>
<main class="app-body flex-box">
  <!-- Article START -->
  <article class="post-article">
    <section class="markdown-content"><h2 id="什么是-panic？"><a href="#什么是-panic？" class="headerlink" title="什么是 panic？"></a>什么是 panic？</h2><p>在 Go 语言中，程序中一般是使用[错误]来处理异常情况。对于程序中出现的大部分异常情况，错误就已经够用了。</p>
<p>但在有些情况，当程序发生异常时，无法继续运行。在这种情况下，我们会使用 <code>panic</code> 来终止程序。当[函数]发生 panic 时，它会终止运行，在执行完所有的[延迟]函数后，程序控制返回到该函数的调用方。这样的过程会一直持续下去，直到当前[协程]的所有函数都返回退出，然后程序会打印出 panic 信息，接着打印出堆栈跟踪（Stack Trace），最后程序终止。在编写一个示例程序后，我们就能很好地理解这个概念了。</p>
<p>在本教程里，我们还会接着讨论，当程序发生 panic 时，使用 <code>recover</code> 可以重新获得对该程序的控制。</p>
<p>可以认为 <code>panic</code> 和 <code>recover</code> 与其他语言中的 <code>try-catch-finally</code> 语句类似，只不过一般我们很少使用 <code>panic</code> 和 <code>recover</code>。而当我们使用了 <code>panic</code> 和 <code>recover</code> 时，也会比 <code>try-catch-finally</code> 更加优雅，代码更加整洁。</p>
<h2 id="什么时候应该使用-panic？"><a href="#什么时候应该使用-panic？" class="headerlink" title="什么时候应该使用 panic？"></a>什么时候应该使用 panic？</h2><p><strong>需要注意的是，你应该尽可能地使用错误，而不是使用 panic 和 recover。只有当程序不能继续运行的时候，才应该使用 panic 和 recover 机制</strong>。</p>
<p>panic 有两个合理的用例。</p>
<ol>
<li><strong>发生了一个不能恢复的错误，此时程序不能继续运行</strong>。  一个例子就是 web 服务器无法绑定所要求的端口。在这种情况下，就应该使用 panic，因为如果不能绑定端口，啥也做不了。</li>
<li><strong>发生了一个编程上的错误</strong>。  假如我们有一个接收指针参数的方法，而其他人使用 <code>nil</code> 作为参数调用了它。在这种情况下，我们可以使用 panic，因为这是一个编程错误：用 <code>nil</code> 参数调用了一个只能接收合法指针的方法。</li>
</ol>
<h2 id="panic-示例"><a href="#panic-示例" class="headerlink" title="panic 示例"></a>panic 示例</h2><p>内建函数 <code>panic</code> 的签名如下所示：</p>
<figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">panic</span><span class="params">(<span class="keyword">interface</span>&#123;&#125;)</span></span></span><br></pre></td></tr></table></figure>

<p>当程序终止时，会打印传入 <code>panic</code> 的参数。我们写一个示例，你就会清楚它的用途了。我们现在就开始吧。</p>
<p>我们会写一个例子，来展示 <code>panic</code> 如何工作。</p>
<figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (  </span><br><span class="line">    <span class="string">"fmt"</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">fullName</span><span class="params">(firstName *<span class="keyword">string</span>, lastName *<span class="keyword">string</span>)</span></span> &#123;  </span><br><span class="line">    <span class="keyword">if</span> firstName == <span class="literal">nil</span> &#123;</span><br><span class="line">        <span class="built_in">panic</span>(<span class="string">"runtime error: first name cannot be nil"</span>)</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">if</span> lastName == <span class="literal">nil</span> &#123;</span><br><span class="line">        <span class="built_in">panic</span>(<span class="string">"runtime error: last name cannot be nil"</span>)</span><br><span class="line">    &#125;</span><br><span class="line">    fmt.Printf(<span class="string">"%s %s\n"</span>, *firstName, *lastName)</span><br><span class="line">    fmt.Println(<span class="string">"returned normally from fullName"</span>)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;  </span><br><span class="line">    firstName := <span class="string">"Elon"</span></span><br><span class="line">    fullName(&amp;firstName, <span class="literal">nil</span>)</span><br><span class="line">    fmt.Println(<span class="string">"returned normally from main"</span>)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>上面的程序很简单，会打印一个人的全名。第 7 行的 <code>fullName</code> 函数会打印出一个人的全名。该函数在第 8 行和第 11 行分别检查了 <code>firstName</code> 和 <code>lastName</code> 的指针是否为 <code>nil</code>。如果是 <code>nil</code>，<code>fullName</code> 函数会调用含有不同的错误信息的 <code>panic</code>。当程序终止时，会打印出该错误信息。</p>
<p>运行该程序，会有如下输出：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">panic: runtime error: last name cannot be nil</span><br><span class="line"></span><br><span class="line">goroutine 1 [running]:  </span><br><span class="line">main.fullName(0x1040c128, 0x0)  </span><br><span class="line">    &#x2F;tmp&#x2F;sandbox135038844&#x2F;main.go:12 +0x120</span><br><span class="line">main.main()  </span><br><span class="line">    &#x2F;tmp&#x2F;sandbox135038844&#x2F;main.go:20 +0x80</span><br></pre></td></tr></table></figure>

<p>我们来分析这个输出，理解一下 panic 是如何工作的，并且思考当程序发生 panic 时，会怎样打印堆栈跟踪。</p>
<p>在第 19 行，我们将 <code>Elon</code> 赋值给了 <code>firstName</code>。在第 20 行，我们调用了 <code>fullName</code> 函数，其中 <code>lastName</code> 等于 <code>nil</code>。因此，满足了第 11 行的条件，程序发生 panic。当出现了 panic 时，程序就会终止运行，打印出传入 panic 的参数，接着打印出堆栈跟踪。因此，第 14 行和第 15 行的代码并不会在发生 panic 之后执行。程序首先会打印出传入 <code>panic</code> 函数的信息：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">panic: runtime error: last name cannot be empty</span><br></pre></td></tr></table></figure>

<p>接着打印出堆栈跟踪。</p>
<p>程序在 <code>fullName</code> 函数的第 12 行发生 panic，因此，首先会打印出如下所示的输出。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">main.fullName(0x1040c128, 0x0)  </span><br><span class="line">    &#x2F;tmp&#x2F;sandbox135038844&#x2F;main.go:12 +0x120</span><br></pre></td></tr></table></figure>

<p>接着会打印出堆栈的下一项。在本例中，堆栈跟踪中的下一项是第 20 行（因为发生 panic 的 <code>fullName</code> 调用就在这一行），因此接下来会打印出：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">main.main()  </span><br><span class="line">    &#x2F;tmp&#x2F;sandbox135038844&#x2F;main.go:20 +0x80</span><br></pre></td></tr></table></figure>

<p>现在我们已经到达了导致 panic 的顶层函数，这里没有更多的层级，因此结束打印。</p>
<h2 id="发生-panic-时的-defer"><a href="#发生-panic-时的-defer" class="headerlink" title="发生 panic 时的 defer"></a>发生 panic 时的 defer</h2><p>我们重新总结一下 panic 做了什么。<strong>当函数发生 panic 时，它会终止运行，在执行完所有的延迟函数后，程序控制返回到该函数的调用方。这样的过程会一直持续下去，直到当前协程的所有函数都返回退出，然后程序会打印出 panic 信息，接着打印出堆栈跟踪，最后程序终止</strong>。</p>
<p>在上面的例子中，我们没有延迟调用任何函数。如果有延迟函数，会先调用它，然后程序控制返回到函数调用方。</p>
<p>我们来修改上面的示例，使用一个延迟语句。</p>
<figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (  </span><br><span class="line">    <span class="string">"fmt"</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">fullName</span><span class="params">(firstName *<span class="keyword">string</span>, lastName *<span class="keyword">string</span>)</span></span> &#123;  </span><br><span class="line">    <span class="keyword">defer</span> fmt.Println(<span class="string">"deferred call in fullName"</span>)</span><br><span class="line">    <span class="keyword">if</span> firstName == <span class="literal">nil</span> &#123;</span><br><span class="line">        <span class="built_in">panic</span>(<span class="string">"runtime error: first name cannot be nil"</span>)</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">if</span> lastName == <span class="literal">nil</span> &#123;</span><br><span class="line">        <span class="built_in">panic</span>(<span class="string">"runtime error: last name cannot be nil"</span>)</span><br><span class="line">    &#125;</span><br><span class="line">    fmt.Printf(<span class="string">"%s %s\n"</span>, *firstName, *lastName)</span><br><span class="line">    fmt.Println(<span class="string">"returned normally from fullName"</span>)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;  </span><br><span class="line">    <span class="keyword">defer</span> fmt.Println(<span class="string">"deferred call in main"</span>)</span><br><span class="line">    firstName := <span class="string">"Elon"</span></span><br><span class="line">    fullName(&amp;firstName, <span class="literal">nil</span>)</span><br><span class="line">    fmt.Println(<span class="string">"returned normally from main"</span>)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>上述代码中，我们只修改了两处，分别在第 8 行和第 20 行添加了延迟函数的调用。</p>
<p>该函数会打印：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">This program prints,</span><br><span class="line"></span><br><span class="line">deferred call in fullName  </span><br><span class="line">deferred call in main  </span><br><span class="line">panic: runtime error: last name cannot be nil</span><br><span class="line"></span><br><span class="line">goroutine 1 [running]:  </span><br><span class="line">main.fullName(0x1042bf90, 0x0)  </span><br><span class="line">    &#x2F;tmp&#x2F;sandbox060731990&#x2F;main.go:13 +0x280</span><br><span class="line">main.main()  </span><br><span class="line">    &#x2F;tmp&#x2F;sandbox060731990&#x2F;main.go:22 +0xc0</span><br></pre></td></tr></table></figure>

<p>当程序在第 13 行发生 panic 时，首先执行了延迟函数，接着控制返回到函数调用方，调用方的延迟函数继续运行，直到到达顶层调用函数。</p>
<p>在我们的例子中，首先执行 <code>fullName</code> 函数中的 <code>defer</code> 语句（第 8 行）。程序打印出：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">deferred call in fullName</span><br></pre></td></tr></table></figure>

<p>接着程序返回到 <code>main</code> 函数，执行了 <code>main</code> 函数的延迟调用，因此会输出：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">deferred call in main</span><br></pre></td></tr></table></figure>

<p>现在程序控制到达了顶层函数，因此该函数会打印出 panic 信息，然后是堆栈跟踪，最后终止程序。</p>
<h2 id="recover"><a href="#recover" class="headerlink" title="recover"></a>recover</h2><p><code>recover</code> 是一个内建函数，用于重新获得 panic 协程的控制。</p>
<p><code>recover</code> 函数的标签如下所示：</p>
<figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">recover</span><span class="params">()</span> <span class="title">interface</span></span>&#123;&#125;</span><br></pre></td></tr></table></figure>

<p>只有在延迟函数的内部，调用 <code>recover</code> 才有用。在延迟函数内调用 <code>recover</code>，可以取到 <code>panic</code> 的错误信息，并且停止 panic 续发事件（Panicking Sequence），程序运行恢复正常。如果在延迟函数的外部调用 <code>recover</code>，就不能停止 panic 续发事件。</p>
<p>我们来修改一下程序，在发生 panic 之后，使用 <code>recover</code> 来恢复正常的运行。</p>
<figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (  </span><br><span class="line">    <span class="string">"fmt"</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">recoverName</span><span class="params">()</span></span> &#123;  </span><br><span class="line">    <span class="keyword">if</span> r := <span class="built_in">recover</span>(); r!= <span class="literal">nil</span> &#123;</span><br><span class="line">        fmt.Println(<span class="string">"recovered from "</span>, r)</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">fullName</span><span class="params">(firstName *<span class="keyword">string</span>, lastName *<span class="keyword">string</span>)</span></span> &#123;  </span><br><span class="line">    <span class="keyword">defer</span> recoverName()</span><br><span class="line">    <span class="keyword">if</span> firstName == <span class="literal">nil</span> &#123;</span><br><span class="line">        <span class="built_in">panic</span>(<span class="string">"runtime error: first name cannot be nil"</span>)</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">if</span> lastName == <span class="literal">nil</span> &#123;</span><br><span class="line">        <span class="built_in">panic</span>(<span class="string">"runtime error: last name cannot be nil"</span>)</span><br><span class="line">    &#125;</span><br><span class="line">    fmt.Printf(<span class="string">"%s %s\n"</span>, *firstName, *lastName)</span><br><span class="line">    fmt.Println(<span class="string">"returned normally from fullName"</span>)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;  </span><br><span class="line">    <span class="keyword">defer</span> fmt.Println(<span class="string">"deferred call in main"</span>)</span><br><span class="line">    firstName := <span class="string">"Elon"</span></span><br><span class="line">    fullName(&amp;firstName, <span class="literal">nil</span>)</span><br><span class="line">    fmt.Println(<span class="string">"returned normally from main"</span>)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>在第 7 行，<code>recoverName()</code> 函数调用了 <code>recover()</code>，返回了调用 <code>panic</code> 的传参。在这里，我们只是打印出 <code>recover</code> 的返回值（第 8 行）。在 <code>fullName</code> 函数内，我们在第 14 行延迟调用了 <code>recoverNames()</code>。</p>
<p>当 <code>fullName</code> 发生 panic 时，会调用延迟函数 <code>recoverName()</code>，它使用了 <code>recover()</code> 来停止 panic 续发事件。</p>
<p>该程序会输出：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">recovered from  runtime error: last name cannot be nil  </span><br><span class="line">returned normally from main  </span><br><span class="line">deferred call in main</span><br></pre></td></tr></table></figure>

<p>当程序在第 19 行发生 panic 时，会调用延迟函数 <code>recoverName</code>，它反过来会调用 <code>recover()</code> 来重新获得 panic 协程的控制。第 8 行调用了 <code>recover</code>，返回了 <code>panic</code> 的传参，因此会打印：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">recovered from  runtime error: last name cannot be nil</span><br></pre></td></tr></table></figure>

<p>在执行完 <code>recover()</code> 之后，panic 会停止，程序控制返回到调用方（在这里就是 <code>main</code> 函数），程序在发生 panic 之后，从第 29 行开始会继续正常地运行。程序会打印 <code>returned normally from main</code>，之后是 <code>deferred call in main</code>。</p>
<h2 id="panic，recover-和-Go-协程"><a href="#panic，recover-和-Go-协程" class="headerlink" title="panic，recover 和 Go 协程"></a>panic，recover 和 Go 协程</h2><p>只有在相同的 [Go 协程]中调用 recover 才管用。<code>recover</code> 不能恢复一个不同协程的 panic。我们用一个例子来理解这一点。</p>
<figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (  </span><br><span class="line">    <span class="string">"fmt"</span></span><br><span class="line">    <span class="string">"time"</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">recovery</span><span class="params">()</span></span> &#123;  </span><br><span class="line">    <span class="keyword">if</span> r := <span class="built_in">recover</span>(); r != <span class="literal">nil</span> &#123;</span><br><span class="line">        fmt.Println(<span class="string">"recovered:"</span>, r)</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">a</span><span class="params">()</span></span> &#123;  </span><br><span class="line">    <span class="keyword">defer</span> recovery()</span><br><span class="line">    fmt.Println(<span class="string">"Inside A"</span>)</span><br><span class="line">    <span class="keyword">go</span> b()</span><br><span class="line">    time.Sleep(<span class="number">1</span> * time.Second)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">b</span><span class="params">()</span></span> &#123;  </span><br><span class="line">    fmt.Println(<span class="string">"Inside B"</span>)</span><br><span class="line">    <span class="built_in">panic</span>(<span class="string">"oh! B panicked"</span>)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;  </span><br><span class="line">    a()</span><br><span class="line">    fmt.Println(<span class="string">"normally returned from main"</span>)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>在上面的程序中，函数 <code>b()</code> 在第 23 行发生 panic。函数 <code>a()</code> 调用了一个延迟函数 <code>recovery()</code>，用于恢复 panic。在第 17 行，函数 <code>b()</code> 作为一个不同的协程来调用。下一行的 <code>Sleep</code> 只是保证 <code>a()</code> 在 <code>b()</code> 运行结束之后才退出。</p>
<p>你认为程序会输出什么？panic 能够恢复吗？答案是否定的，panic 并不会恢复。因为调用 <code>recovery</code> 的协程和 <code>b()</code> 中发生 panic 的协程并不相同，因此不可能恢复 panic。</p>
<p>运行该程序会输出：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">Inside A  </span><br><span class="line">Inside B  </span><br><span class="line">panic: oh! B panicked</span><br><span class="line"></span><br><span class="line">goroutine 5 [running]:  </span><br><span class="line">main.b()  </span><br><span class="line">    &#x2F;tmp&#x2F;sandbox388039916&#x2F;main.go:23 +0x80</span><br><span class="line">created by main.a  </span><br><span class="line">    &#x2F;tmp&#x2F;sandbox388039916&#x2F;main.go:17 +0xc0</span><br></pre></td></tr></table></figure>

<p>从输出可以看出，panic 没有恢复。</p>
<p>如果函数 <code>b()</code> 在相同的协程里调用，panic 就可以恢复。</p>
<p>如果程序的第 17 行由 <code>go b()</code> 修改为 <code>b()</code>，就可以恢复 panic 了，因为 panic 发生在与 recover 相同的协程里。如果运行这个修改后的程序，会输出：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">Inside A  </span><br><span class="line">Inside B  </span><br><span class="line">recovered: oh! B panicked  </span><br><span class="line">normally returned from main</span><br></pre></td></tr></table></figure>

<h2 id="运行时-panic"><a href="#运行时-panic" class="headerlink" title="运行时 panic"></a>运行时 panic</h2><p>运行时错误（如数组越界）也会导致 panic。这等价于调用了内置函数 <code>panic</code>，其参数由接口类型 [runtime.Error]给出。<code>runtime.Error</code> 接口的定义如下：</p>
<figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">type</span> Error <span class="keyword">interface</span> &#123;  </span><br><span class="line">    error</span><br><span class="line">    <span class="comment">// RuntimeError is a no-op function but</span></span><br><span class="line">    <span class="comment">// serves to distinguish types that are run time</span></span><br><span class="line">    <span class="comment">// errors from ordinary errors: a type is a</span></span><br><span class="line">    <span class="comment">// run time error if it has a RuntimeError method.</span></span><br><span class="line">    RuntimeError()</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>而 <code>runtime.Error</code> 接口满足内建接口类型 [<code>error</code>]。</p>
<p>我们来编写一个示例，创建一个运行时 panic。</p>
<figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (  </span><br><span class="line">    <span class="string">"fmt"</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">a</span><span class="params">()</span></span> &#123;  </span><br><span class="line">    n := []<span class="keyword">int</span>&#123;<span class="number">5</span>, <span class="number">7</span>, <span class="number">4</span>&#125;</span><br><span class="line">    fmt.Println(n[<span class="number">3</span>])</span><br><span class="line">    fmt.Println(<span class="string">"normally returned from a"</span>)</span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;  </span><br><span class="line">    a()</span><br><span class="line">    fmt.Println(<span class="string">"normally returned from main"</span>)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>在上面的程序中，第 9 行我们试图访问 <code>n[3]</code>，这是一个对[切片]的错误引用。该程序会发生 panic，输出如下：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">panic: runtime error: index out of range</span><br><span class="line"></span><br><span class="line">goroutine 1 [running]:  </span><br><span class="line">main.a()  </span><br><span class="line">    &#x2F;tmp&#x2F;sandbox780439659&#x2F;main.go:9 +0x40</span><br><span class="line">main.main()  </span><br><span class="line">    &#x2F;tmp&#x2F;sandbox780439659&#x2F;main.go:13 +0x20</span><br></pre></td></tr></table></figure>

<p>你也许想知道，是否可以恢复一个运行时 panic？当然可以！我们来修改一下上面的代码，恢复这个 panic。</p>
<figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (  </span><br><span class="line">    <span class="string">"fmt"</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">r</span><span class="params">()</span></span> &#123;  </span><br><span class="line">    <span class="keyword">if</span> r := <span class="built_in">recover</span>(); r != <span class="literal">nil</span> &#123;</span><br><span class="line">        fmt.Println(<span class="string">"Recovered"</span>, r)</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">a</span><span class="params">()</span></span> &#123;  </span><br><span class="line">    <span class="keyword">defer</span> r()</span><br><span class="line">    n := []<span class="keyword">int</span>&#123;<span class="number">5</span>, <span class="number">7</span>, <span class="number">4</span>&#125;</span><br><span class="line">    fmt.Println(n[<span class="number">3</span>])</span><br><span class="line">    fmt.Println(<span class="string">"normally returned from a"</span>)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;  </span><br><span class="line">    a()</span><br><span class="line">    fmt.Println(<span class="string">"normally returned from main"</span>)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>运行上面程序会输出：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">Recovered runtime error: index out of range  </span><br><span class="line">normally returned from main</span><br></pre></td></tr></table></figure>

<p>从输出可以知道，我们已经恢复了这个 panic。</p>
<h2 id="恢复后获得堆栈跟踪"><a href="#恢复后获得堆栈跟踪" class="headerlink" title="恢复后获得堆栈跟踪"></a>恢复后获得堆栈跟踪</h2><p>当我们恢复 panic 时，我们就释放了它的堆栈跟踪。实际上，在上述程序里，恢复 panic 之后，我们就失去了堆栈跟踪。</p>
<p>有办法可以打印出堆栈跟踪，就是使用 [<code>Debug</code>]包中的 [<code>PrintStack</code>]函数。</p>
<figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (  </span><br><span class="line">    <span class="string">"fmt"</span></span><br><span class="line">    <span class="string">"runtime/debug"</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">r</span><span class="params">()</span></span> &#123;  </span><br><span class="line">    <span class="keyword">if</span> r := <span class="built_in">recover</span>(); r != <span class="literal">nil</span> &#123;</span><br><span class="line">        fmt.Println(<span class="string">"Recovered"</span>, r)</span><br><span class="line">        debug.PrintStack()</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">a</span><span class="params">()</span></span> &#123;  </span><br><span class="line">    <span class="keyword">defer</span> r()</span><br><span class="line">    n := []<span class="keyword">int</span>&#123;<span class="number">5</span>, <span class="number">7</span>, <span class="number">4</span>&#125;</span><br><span class="line">    fmt.Println(n[<span class="number">3</span>])</span><br><span class="line">    fmt.Println(<span class="string">"normally returned from a"</span>)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> &#123;  </span><br><span class="line">    a()</span><br><span class="line">    fmt.Println(<span class="string">"normally returned from main"</span>)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>在上面的程序中，我们在第 11 行使用了 <code>debug.PrintStack()</code> 打印堆栈跟踪。</p>
<p>该程序会输出：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">Recovered runtime error: index out of range  </span><br><span class="line">goroutine 1 [running]:  </span><br><span class="line">runtime&#x2F;debug.Stack(0x1042beb8, 0x2, 0x2, 0x1c)  </span><br><span class="line">    &#x2F;usr&#x2F;local&#x2F;go&#x2F;src&#x2F;runtime&#x2F;debug&#x2F;stack.go:24 +0xc0</span><br><span class="line">runtime&#x2F;debug.PrintStack()  </span><br><span class="line">    &#x2F;usr&#x2F;local&#x2F;go&#x2F;src&#x2F;runtime&#x2F;debug&#x2F;stack.go:16 +0x20</span><br><span class="line">main.r()  </span><br><span class="line">    &#x2F;tmp&#x2F;sandbox949178097&#x2F;main.go:11 +0xe0</span><br><span class="line">panic(0xf0a80, 0x17cd50)  </span><br><span class="line">    &#x2F;usr&#x2F;local&#x2F;go&#x2F;src&#x2F;runtime&#x2F;panic.go:491 +0x2c0</span><br><span class="line">main.a()  </span><br><span class="line">    &#x2F;tmp&#x2F;sandbox949178097&#x2F;main.go:18 +0x80</span><br><span class="line">main.main()  </span><br><span class="line">    &#x2F;tmp&#x2F;sandbox949178097&#x2F;main.go:23 +0x20</span><br><span class="line">normally returned from main</span><br></pre></td></tr></table></figure>

<p>从输出我们可以看出，首先已经恢复了 panic，打印出 <code>Recovered runtime error: index out of range</code>。此外，我们也打印出了堆栈跟踪。在恢复了 panic 之后，还打印出 <code>normally returned from main</code>。</p>
<p>本教程到此结束。</p>
<p>简单概括一下本教程讨论的内容：</p>
<ul>
<li>什么是 panic？</li>
<li>什么时候应该使用 panic？</li>
<li>panic 示例</li>
<li>发生 panic 时的 defer</li>
<li>recover</li>
<li>panic，recover 和 Go 协程</li>
<li>运行时 panic</li>
<li>恢复后获得堆栈跟踪</li>
</ul>
</section>
    <!-- Tags START -->
    
      <div class="tags">
        <span>Tags:</span>
        
  <a href="/tags#Go" >
    <span class="tag-code">Go</span>
  </a>

      </div>
    
    <!-- Tags END -->
    <!-- NAV START -->
    
  <div class="nav-container">
    <!-- reverse left and right to put prev and next in a more logic postition -->
    
      <a class="nav-left" href="/go/2-Go%E8%AF%AD%E8%A8%80%E5%9F%BA%E7%A1%80/33-%E5%87%BD%E6%95%B0%E6%98%AF%E4%B8%80%E7%AD%89%E5%85%AC%E6%B0%91%EF%BC%88%E5%A4%B4%E7%AD%89%E5%87%BD%E6%95%B0%EF%BC%89/">
        <span class="nav-arrow">← </span>
        
          33-函数是一等公民（头等函数）
        
      </a>
    
    
      <a class="nav-right" href="/go/4-Go%E8%AF%AD%E8%A8%80%E6%A1%86%E6%9E%B6/02-Web%E6%A1%86%E6%9E%B6%E4%B9%8BBeego/">
        
          Web框架之Beego
        
        <span class="nav-arrow"> →</span>
      </a>
    
  </div>

    <!-- NAV END -->
    <!-- 打赏 START -->
    
      <div class="money-like">
        <div class="reward-btn">
          赏
          <span class="money-code">
            <span class="alipay-code">
              <div class="code-image"></div>
              <b>使用支付宝打赏</b>
            </span>
            <span class="wechat-code">
              <div class="code-image"></div>
              <b>使用微信打赏</b>
            </span>
          </span>
        </div>
        <p class="notice">点击上方按钮,请我喝杯咖啡！</p>
      </div>
    
    <!-- 打赏 END -->
    <!-- 二维码 START -->
    
      <div class="qrcode">
        <canvas id="share-qrcode"></canvas>
        <p class="notice">扫描二维码，分享此文章</p>
      </div>
    
    <!-- 二维码 END -->
    
      <!-- Gitment START -->
      <div id="comments"></div>
      <!-- Gitment END -->
    
  </article>
  <!-- Article END -->
  <!-- Catalog START -->
  
    <aside class="catalog-container">
  <div class="toc-main">
  <!-- 不蒜子统计 -->
    <strong class="toc-title">目录</strong>
    
      <ol class="toc-nav"><li class="toc-nav-item toc-nav-level-2"><a class="toc-nav-link" href="#什么是-panic？"><span class="toc-nav-text">什么是 panic？</span></a></li><li class="toc-nav-item toc-nav-level-2"><a class="toc-nav-link" href="#什么时候应该使用-panic？"><span class="toc-nav-text">什么时候应该使用 panic？</span></a></li><li class="toc-nav-item toc-nav-level-2"><a class="toc-nav-link" href="#panic-示例"><span class="toc-nav-text">panic 示例</span></a></li><li class="toc-nav-item toc-nav-level-2"><a class="toc-nav-link" href="#发生-panic-时的-defer"><span class="toc-nav-text">发生 panic 时的 defer</span></a></li><li class="toc-nav-item toc-nav-level-2"><a class="toc-nav-link" href="#recover"><span class="toc-nav-text">recover</span></a></li><li class="toc-nav-item toc-nav-level-2"><a class="toc-nav-link" href="#panic，recover-和-Go-协程"><span class="toc-nav-text">panic，recover 和 Go 协程</span></a></li><li class="toc-nav-item toc-nav-level-2"><a class="toc-nav-link" href="#运行时-panic"><span class="toc-nav-text">运行时 panic</span></a></li><li class="toc-nav-item toc-nav-level-2"><a class="toc-nav-link" href="#恢复后获得堆栈跟踪"><span class="toc-nav-text">恢复后获得堆栈跟踪</span></a></li></ol>
    
  </div>
</aside>
  
  <!-- Catalog END -->
</main>

<script>
  (function () {
    var url = 'http://www.liuqingzheng.top/go/2-Go语言基础/32-panic和recover/';
    var banner = ''
    if (banner !== '' && banner !== 'undefined' && banner !== 'null') {
      $('#article-banner').css({
        'background-image': 'url(' + banner + ')'
      })
    } else {
      $('#article-banner').geopattern(url)
    }
    $('.header').removeClass('fixed-header')

    // error image
    $(".markdown-content img").on('error', function() {
      $(this).attr('src', 'http://file.muyutech.com/error-img.png')
      $(this).css({
        'cursor': 'default'
      })
    })

    // zoom image
    $(".markdown-content img").on('click', function() {
      var src = $(this).attr('src')
      if (src !== 'http://file.muyutech.com/error-img.png') {
        var imageW = $(this).width()
        var imageH = $(this).height()

        var zoom = ($(window).width() * 0.95 / imageW).toFixed(2)
        zoom = zoom < 1 ? 1 : zoom
        zoom = zoom > 2 ? 2 : zoom
        var transY = (($(window).height() - imageH) / 2).toFixed(2)

        $('body').append('<div class="image-view-wrap"><div class="image-view-inner"><img src="'+ src +'" /></div></div>')
        $('.image-view-wrap').addClass('wrap-active')
        $('.image-view-wrap img').css({
          'width': `${imageW}`,
          'transform': `translate3d(0, ${transY}px, 0) scale3d(${zoom}, ${zoom}, 1)`
        })
        $('html').css('overflow', 'hidden')

        $('.image-view-wrap').on('click', function() {
          $(this).remove()
          $('html').attr('style', '')
        })
      }
    })
  })();
</script>


  <script>
    var qr = new QRious({
      element: document.getElementById('share-qrcode'),
      value: document.location.href
    });
  </script>



  <script>
    var gitmentConfig = "liuqingzheng";
    if (gitmentConfig !== 'undefined') {
      var gitment = new Gitment({
        id: "32-panic和recover",
        owner: "liuqingzheng",
        repo: "FuckBlog",
        oauth: {
          client_id: "32a4076431cf39d0ecea",
          client_secret: "94484bd79b3346a949acb2fda3c8a76ce16990c6"
        },
        theme: {
          render(state, instance) {
            const container = document.createElement('div')
            container.lang = "en-US"
            container.className = 'gitment-container gitment-root-container'
            container.appendChild(instance.renderHeader(state, instance))
            container.appendChild(instance.renderEditor(state, instance))
            container.appendChild(instance.renderComments(state, instance))
            container.appendChild(instance.renderFooter(state, instance))
            return container;
          }
        }
      })
      gitment.render(document.getElementById('comments'))
    }
  </script>




    <div class="scroll-top">
  <span class="arrow-icon"></span>
</div>
    <footer class="app-footer">
<!-- 不蒜子统计 -->
<span id="busuanzi_container_site_pv">
     本站总访问量<span id="busuanzi_value_site_pv"></span>次
</span>
<span class="post-meta-divider">|</span>
<span id="busuanzi_container_site_uv" style='display:none'>
     本站访客数<span id="busuanzi_value_site_uv"></span>人
</span>
<script async src="//busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js"></script>



  <p class="copyright">
    &copy; 2021 | Proudly powered by <a href="https://www.cnblogs.com/xiaoyuanqujing" target="_blank">小猿取经</a>
    <br>
    Theme by <a href="https://www.cnblogs.com/xiaoyuanqujing" target="_blank" rel="noopener">小猿取经</a>
  </p>
</footer>

<script>
  function async(u, c) {
    var d = document, t = 'script',
      o = d.createElement(t),
      s = d.getElementsByTagName(t)[0];
    o.src = u;
    if (c) { o.addEventListener('load', function (e) { c(null, e); }, false); }
    s.parentNode.insertBefore(o, s);
  }
</script>
<script>
  async("//cdnjs.cloudflare.com/ajax/libs/fastclick/1.0.6/fastclick.min.js", function(){
    FastClick.attach(document.body);
  })
</script>

<script>
  var hasLine = 'true';
  async("//cdnjs.cloudflare.com/ajax/libs/highlight.js/9.12.0/highlight.min.js", function(){
    $('figure pre').each(function(i, block) {
      var figure = $(this).parents('figure');
      if (hasLine === 'false') {
        figure.find('.gutter').hide();
      }
      var lang = figure.attr('class').split(' ')[1] || 'code';
      var codeHtml = $(this).html();
      var codeTag = document.createElement('code');
      codeTag.className = lang;
      codeTag.innerHTML = codeHtml;
      $(this).attr('class', '').empty().html(codeTag);
      figure.attr('data-lang', lang.toUpperCase());
      hljs.highlightBlock(block);
    });
  })
</script>





<!-- Baidu Tongji -->

<script>
    var _baId = 'c5fd96eee1193585be191f318c3fa725';
    // Originial
    var _hmt = _hmt || [];
    (function() {
      var hm = document.createElement("script");
      hm.src = "//hm.baidu.com/hm.js?" + _baId;
      var s = document.getElementsByTagName("script")[0];
      s.parentNode.insertBefore(hm, s);
    })();
</script>


<script src="/js/script.js"></script>


<script src="/js/search.js"></script>


<script src="/js/load.js"></script>



  <span class="local-search local-search-google local-search-plugin" style="right: 50px;top: 70px;;position:absolute;z-index:2;">
      <input type="search" placeholder="站内搜索" id="local-search-input" class="local-search-input-cls" style="">
      <div id="local-search-result" class="local-search-result-cls"></div>
  </span>


  </body>
</html>